home *** CD-ROM | disk | FTP | other *** search
/ Chip 2007 January, February, March & April / Chip-Cover-CD-2007-02.iso / Pakiet multimedia / Grafika i zdjecia / Edytory grafiki rastrowej i wektorowej / Inscape 0.44.1 / Inkscape-0.44.1-1.win32.exe / share / extensions / SpSVG.pm < prev    next >
Text File  |  2006-09-06  |  9KB  |  352 lines

  1. #!/usr/bin/perl -w
  2. #
  3. # SpSVG
  4. # Perl module for sodipodi extensions
  5. #
  6. # This is a temporary hack that provides the following:
  7. #   * Some standard getopts (help, i/o, ids)
  8. #   * A way to exit that produces the error codes outlined in
  9. #     the extension specs (SpSVG::error)
  10. #   * A method that takes a function as its arguments and passes
  11. #     each specified element ('--id=foo --id=bar', 'ids=fooz,baaz',
  12. #     and so forth) as plain text to the function. The function is 
  13. #     expected to return the processed version of this text.
  14. #     
  15. # TODO:
  16. #
  17. #   * Write POD
  18. #   * Exit with a friendly message if XML::XQL isn't installed
  19. #   * Decide how to implement the module interface
  20. #   * Move from XML::XQL to SVG/SVG::Parser (see below)
  21. #   * Make the process method more efficient (again, see below)
  22. #
  23. # Authors: Daniel Goude (goude@dtek.chalmers.se)
  24. #
  25.  
  26. package SpSVG; # Think of a better name
  27. use strict;
  28. #use Carp;
  29. use Exporter;
  30. use Getopt::Long;
  31. #use Data::Dumper; # For debugging
  32.  
  33. # From the SVG.pm documentation (actually 
  34. # http://roasp.com/tutorial/tutorial6.shtml):
  35. #
  36. # > Currently, version 2.0 of SVG.pm does not internally support DOM
  37. # > traversiong functionality such as getting the children,siblings,or
  38. # > parent of an element, so the interaction capability between SVG::Parser
  39. # > and SVG is limited to manipulations of a known image. The next version
  40. # > of SVG will support all these and more key functions which will make
  41. # > SVG::Parser extremely useful.
  42. #
  43. # I plan to replace the /XML::XQL(::DOM)?/ code as soon as this is
  44. # fixed.
  45.  
  46. #use SVG;
  47. #use SVG::Parser;
  48.  
  49. use XML::XQL;
  50. use XML::XQL::DOM;
  51.  
  52. use vars qw(@ISA @EXPORT $VERSION);
  53.  
  54. $VERSION = 1.02; # fixme: use SpSVG 1.01 doesn't raise exception.
  55. @ISA = qw(Exporter);
  56.  
  57. # Symbols 
  58. @EXPORT = qw(
  59.  
  60. ); 
  61.  
  62. sub new {
  63.     my $self = {
  64.         status   => make_status(),
  65.         name     => '',      # Name of script
  66.         usage    => '',      # Usage string
  67.         opt_help => [],      # Used for --help
  68.         
  69.         ids     => [],       # Array of ids that will be iterated over 
  70.                              # in process()
  71.         svg     => '',       # SVG document object
  72.         
  73.     };
  74.     bless $self;
  75. }
  76.  
  77. sub parse {
  78.     my $self = shift;
  79.     
  80.     my $infile = $self->{'opts'}->{'file'};
  81.  
  82.     my $xml;
  83.     {
  84.         local $/=undef;
  85.         if ($infile) {
  86.             open (IN, $infile) or 
  87.                 $self->error('IO_ERR', "Can't open $infile: $!\n");
  88.             $xml = <IN>;
  89.             close IN or 
  90.                 $self->error('IO_ERR', "Can't close $infile: $!\n");
  91.         } else {
  92.             $xml = <>;
  93.         }
  94.     }
  95.  
  96.  
  97.     $self->{'parser'} = new XML::DOM::Parser;
  98.     my $parser = $self->{'parser'};
  99.     my $svg = $parser->parse($xml) ||
  100.             $self->error('INPUT_ERR', "Couldn't parse input: $!.");
  101.     $self->{'svg'} = $svg;
  102. }
  103.  
  104. # Return SVG document as a string
  105. sub get {
  106.     my $self = shift;
  107.     my $string =  $self->{'svg'}->toString;
  108.     
  109. }
  110.  
  111. # Print to $outfile|STDOUT
  112. sub dump {
  113.     my $self = shift;
  114.     my $outfile = $self->{'opts'}->{'output'};
  115.     if ($outfile) {
  116.         open(OUT, ">$outfile") or 
  117.             $self->error('IO_ERR', "Can't open $outfile for writing: $!\n");
  118.         print OUT $self->get;
  119.         close OUT or $self->error('IO_ERR', "Can't close $outfile: $!\n");
  120.     } else {
  121.         print $self->get;
  122.     }
  123. }
  124.  
  125. sub process_ids {
  126.     my $self = shift;
  127.     my $func = shift;
  128.  
  129.     my @ids = @{$self->{'ids'}};
  130.  
  131.     # Apply a user supplied function to each id
  132.     foreach my $id (@ids) {
  133.         my $svg = $self->{'svg'};
  134.         #warn "ID: $id\n";
  135.         my @nodes = $svg->xql("//*[\@id = '$id']") or
  136.             $self->error('NOOP_ERR', "Couldn't find element $id.");
  137.         my $node = shift @nodes; # Ids are unique
  138.                                  # fixme: Add more checking.
  139.  
  140.         # Call the user function on the node identified by $id
  141.         my $new_node = $func->($node->toString);
  142.     
  143.         # Replace the comment with user generated SVG
  144.         my $parent = $node->getParentNode;
  145.         my $comment = $svg->createComment('SpSVG');
  146.         $parent->replaceChild($comment, $node);
  147.         my $output =  $self->{'svg'}->toString;
  148.         $output =~ s/<!--SpSVG-->/$new_node/;
  149.  
  150.         # Here the whole (new) document is parsed. Probably VERY inefficient,
  151.         # but at least you get syntax checking for free..
  152.         $self->{'svg'} = $self->{'parser'}->parse($output);
  153.         #print $self->{'svg'}->toString;
  154.     }
  155.  
  156.     
  157.  
  158. # Exit status codes
  159. sub make_status {
  160.     my $self = shift;
  161.     my %status = (
  162.         0 => ["SUCCESS", "Extension exited gracefully"],
  163.         1 => ["GEN_FAIL", "General failure"],
  164.         2 => ["MEM_ERR", "Memory error"],
  165.         3 => ["IO_ERR", "File I/O error"],
  166.         4 => ["MATH_ERR", "Math error"],
  167.         5 => ["INPUT_ERR", "Input not understood (not valid SVG)"],
  168.         6 => ["NOOP_ERR", "Could not operate on any objects in this " . 
  169.             "data stream"],
  170.         7 => ["ARG_ERR", "Incorrect script arguments"]
  171.     );
  172.  
  173.     # Generate error subs dynamically
  174.     foreach my $exit_code (sort keys %status) {
  175.         eval "sub $status{$exit_code}[0] { $exit_code; }";
  176.         die $@ if $@;
  177.     }
  178.     return \%status;
  179.  
  180. }
  181.  
  182. # Create an option array suitable for Getopt::Long
  183. sub make_opt_vals {
  184.     my $self = shift;
  185.     my @opt_desc = @_;
  186.     my @opt_vals;
  187.     my @opt_help = @{$self->{'opt_help'}};
  188.     foreach (@opt_desc) {
  189.         my %h = %$_;
  190.         foreach my $key (keys %h) {
  191.             #print "Key : $h{$key}\n";
  192.             if ($key eq 'opt') {
  193.                 push @opt_vals, $h{'opt'};
  194.             } elsif ($key eq 'desc') {
  195.                 my $option = $h{'opt'};
  196.                 $option =~ s/([^=]+)=.+/$1/;
  197.                 $option =~ s/([^|]+)/(length "$1" > 1 ? '--' : '-') . "$1"/eg;
  198.                 push @opt_help, [$option, $h{'desc'}];
  199.             }
  200.         }
  201.     }
  202.     $self->{'opt_help'} = \@opt_help;
  203.     return @opt_vals;
  204. }
  205.  
  206. # Parse command line options
  207. sub get_opts {
  208.     my $self = shift;
  209.     my @user_opt_desc = @_;
  210.    
  211.     my @opt_desc = (
  212.         {
  213.             opt => 'help|h',
  214.             desc => 'Display this help and exit.',
  215.         },
  216.         
  217.         {
  218.             opt => 'version|v',
  219.             desc => 'Print version and exit.',
  220.         },           
  221.         
  222.         {
  223.             opt => 'file|F=s',
  224.             desc => 'Input file (default: STDIN).',
  225.         },            
  226.         
  227.         {
  228.             opt => 'output|o=s',
  229.             desc => 'Output file (default: STDOUT).',
  230.         },
  231.         
  232.         {
  233.             opt => 'id=s@',
  234.             desc => 'svg id to operate on (can be multiple).',
  235.         },           
  236.         
  237.         {   
  238.             opt => 'ids=s',
  239.             desc => 'Comma-separated list of svg ids to operate on.',
  240.         },           
  241.     );
  242.  
  243.     # Create option arrays for Getopt::Long
  244.     my @opt_vals = $self->make_opt_vals(@opt_desc);
  245.     my @user_opt_vals = $self->make_opt_vals(@user_opt_desc);
  246.     
  247.     # Append user options 
  248.     foreach (@user_opt_vals) {
  249.         push @opt_vals, $_;
  250.     }
  251.     
  252.     # Where the parsed options are stored
  253.     my %opts;
  254.  
  255.     #exit 0;
  256.  
  257.     # Parse all options
  258.     GetOptions(\%opts, @opt_vals) or usage();    
  259.  
  260.     # Handle comma-separated 'ids=foo,bar'
  261.     my @ids = @{$opts{'id'}} if $opts{'id'};
  262.     if (exists $opts{'ids'} && $opts{'ids'} =~ /[\w\d_]+(,[\w\d_]+)*/) {
  263.         push (@ids, split(/,/, $opts{'ids'}));
  264.     }
  265.  
  266.     # Display usage etc. (and exit)
  267.     exists $opts{'version'} && $self->version();
  268.     exists $opts{'help'} && $self->usage(); 
  269.  
  270.     # Save id values for later processing 
  271.     $self->{'ids'} = \@ids;
  272.     
  273.     # Save options
  274.     $self->{'opts'} = \%opts;
  275.  
  276.     # Return the options to script
  277.     return %opts;
  278. }
  279.  
  280. # Exit with named exit status
  281. sub error {
  282.     my $self = shift;
  283.     my $error_name = shift;
  284.     my $script_error_msg = shift || '';
  285.    
  286.     my %status = %{$self->{'status'}};
  287.  
  288.     foreach (keys %status) {
  289.         if ($status{$_}[0] eq $error_name) {
  290.             $! = $_; # Set exit status
  291.  
  292.             # Commented out; let sodipodi handle the error code instead
  293.             #my $msg =  ($status{$_}->[1] . ": $script_error_msg");
  294.             
  295.             my $msg =  "$script_error_msg";
  296.             die $msg;
  297.         }
  298.     }
  299.     
  300.     # Will not be reached unless an improper error_name is given
  301.     $! = 255; # Exit status 
  302.     warn "Illegal error code '$error_name' called from script\n";
  303. }
  304.  
  305. # Some accessor methods
  306. sub set_usage {
  307.     my $self = shift;
  308.     my $usage = shift || die "No usage string supplied!\n";
  309.     $self->{'usage'} = $usage;
  310. }
  311.  
  312. sub set_name {
  313.     my $self = shift;
  314.     my $name = shift || die "No script name supplied!\n";
  315.     $self->{'name'} = $name;
  316. }
  317.  
  318. # Print usage and exit
  319. sub usage {
  320.     my $self = shift;
  321.     print "Usage: $self->{'name'} OPTIONS FILE\n";
  322.     print $self->{'usage'};
  323.     
  324.     my @opt_help = @{$self->{'opt_help'}};
  325.     foreach (@opt_help) {
  326.         print pad($_->[0]) . $_->[1] . "\n";
  327.     }
  328.  
  329.     exit ARG_ERR(); 
  330. }
  331.  
  332. sub pad {
  333.     my $string = shift;
  334.     my $width = '20';
  335.     return $string . ' ' x ($width - length($string));
  336. }
  337.  
  338. # Print version
  339. sub version {
  340.     print "Uses SpSVG version $VERSION\n";
  341.     exit ARG_ERR();
  342. }
  343.  
  344. # End of module; return something true
  345. 1;
  346.  
  347. __END__
  348.  
  349. DOCUMENTATION HERE
  350.